/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：登录通-系统通用工具封装层
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taiping.dlt.application.rmi;

import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.taipingframework.boot.cache.constant.DurationConstant;
import com.taipingframework.boot.cache.redis.StandardCacheKeyBuilder;
import com.taipingframework.boot.web.response.result.ApiResult;
import com.taipingframework.boot.web.response.status.ApiStatusEnum;
import com.taipingframework.utility.constant.ApplicationConstant;
import com.taipingframework.utility.exception.ClientException;
import com.taipingframework.utility.exception.SystemInterrupterException;
import com.taipingframework.utility.exception.SystemMessageException;
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.TimeoutException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.CodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.netty.http.client.HttpClient;

import java.io.Serializable;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

/**
 * WebClient工具类
 */
@Slf4j
public class WebClientKit {

    /**
     * 处理远程接口返回结果
     */
    public static <T extends Serializable> T apiResultHandle(ApiResult<T> apiResult, String requestId, String baseUrl, String apiPath) {
        String apiResultJson = JSONUtil.toJsonStr(apiResult);
        log.info("[" + requestId + "]调用接口{}{}返回结果为：{}", baseUrl, apiPath, apiResultJson);
        if (apiResult.getSuccess()) {
            return apiResult.getResult();
        }
        // 默认的异常处理程序可以拿到该属性，从而告诉客户端具体出现了什么异常
        // serverWebExchange.getAttributes().put(ApplicationConstant.RESPONSE_API_RESULT, apiResultJson);
        throw new SystemMessageException(apiResult);
    }

    /**
     * 重新包装异常消息
     */
    public static Throwable newErrorResume(Throwable error) {
        if (error instanceof TimeoutException) {
            error = new SystemInterrupterException(ApiStatusEnum.ERR_B0100);
        }
        return new ClientException(error);
    }

    /**
     * 自定义WebClient远程HTTP接口请求对象
     */
    public static WebClient createWebClient(ServerWebExchange serverWebExchange, String baseUrl, ObjectMapper objectMapper) {
        // 执行 bodyToMono(new ParameterizedTypeReference<ApiResult<AuthLoginBO>>() {}) 这一行代码时，
        // 当底层在进行字符串转LocalDateTime类型时抛异常，此处配置使用自定义的ObjectMapper解决这个问题！
        ExchangeStrategies strategies = ExchangeStrategies.builder()
                .codecs(clientCodecConfigurer -> {
                    CodecConfigurer.CustomCodecs customCodecs = clientCodecConfigurer.customCodecs();
                    customCodecs.registerWithDefaultConfig(new Jackson2JsonDecoder(objectMapper));
                })
                .build();
        long timeout = TimeUnit.SECONDS.toMillis(DurationConstant.SEC_5.seconds());
        HttpClient httpClient = HttpClient.create()
                .tcpConfiguration(tcpClient -> tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) timeout))
                .responseTimeout(Duration.ofMillis(timeout));
        return WebClient.builder()
                .exchangeStrategies(strategies)
                // 自定义HttpClient - 设置连接超时和响应超时
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                // 自定义过滤器 - 重写请求头和响应头
                .filter((clientRequest, exchangeFunction) -> exchangeFunction
                        .exchange(newClientRequest(clientRequest, serverWebExchange))
                        .doOnSuccess(clientResponse -> newClientResponse(clientResponse, serverWebExchange)))
                .baseUrl(baseUrl)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();
    }

    /**
     * 重构 ClientRequest 请求头
     */
    static ClientRequest newClientRequest(ClientRequest clientRequest, ServerWebExchange exchange) {
        // 设置当前请求的唯一标识
        String requestId = exchange.getAttribute(ApplicationConstant.RESPONSE_IDENTITY);
        // 当前会话的唯一标识
        String accessToken = exchange.getAttribute(StandardCacheKeyBuilder.getAuthTokenKey());

        // 调用远程接口，重新设置请求头
        ClientRequest.Builder clientBuilder = ClientRequest.from(clientRequest);
        clientBuilder.header(ApplicationConstant.RESPONSE_IDENTITY, requestId);
        if (StringUtils.isNotEmpty(accessToken)) {
            clientBuilder.header(StandardCacheKeyBuilder.getAuthTokenKey(), accessToken);
        }
        return clientBuilder.build();
    }

    /**
     * 重构 ClientResponse 响应头
     */
    static void newClientResponse(ClientResponse clientResponse, ServerWebExchange exchange) {
        // 获取到远程HTTP服务的响应头
        HttpHeaders httpHeaders = clientResponse.headers().asHttpHeaders();
        String responseId = httpHeaders.getFirst(ApplicationConstant.RESPONSE_IDENTITY);
        // 将存储请求标识的响应参数回写到当前HTTP请求的响应头
        exchange.getResponse()
                .getHeaders()
                .add(ApplicationConstant.RESPONSE_IDENTITY, responseId);
    }

}
